﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Mono.Cecil;
using devtm.Aop.Extensions;

namespace devtm.Aop.Copy
{
    
    public class CopyMethod
    {


        private MethodReference method;
        private MethodDefinition methodDef;

        public CopyMethod(MethodReference sourceMethod)
        {
            this.method = sourceMethod;
            methodDef = method.GetDefinition();
        }
        
        public MethodDefinition CloneMethod(TypeReference targetTypeDeclaringMethod, TypeDefinition proxyType, bool autoAddInproxyType, string name = "")
        {

            if (string.IsNullOrEmpty(name))
                name = method.Name;

            var retour = method.ReturnType.CloseGenericParameter(targetTypeDeclaringMethod, proxyType);

            var methodDefinition = new MethodDefinition(name, methodDef.Attributes, retour)
            {
                //Body = { InitLocals = init },
                IsHideBySig = methodDef.IsHideBySig,
                IsStatic = methodDef.IsStatic,
                IsAbstract = methodDef.IsAbstract,
                IsFinal = methodDef.IsFinal,
                IsVirtual = methodDef.IsVirtual,
                IsPrivate = methodDef.IsPrivate,
                IsIL = methodDef.IsIL,
                IsInternalCall = methodDef.IsInternalCall,
                IsManaged = methodDef.IsManaged,
                DeclaringType = proxyType,
                ImplAttributes = methodDef.ImplAttributes,
                ExplicitThis = methodDef.ExplicitThis,
                CallingConvention = method.CallingConvention,
            };

            methodDefinition.Body = new Mono.Cecil.Cil.MethodBody(methodDefinition);
            methodDefinition.Body.InitLocals = methodDef.Body != null ? methodDef.Body.InitLocals : false;


            proxyType.Module.Import(method.ReturnType);

            methodDefinition.Overrides.AddRange(methodDef.Overrides);

            Clone(methodDef.CustomAttributes.ToList(), methodDefinition);

            if (methodDef.Body != null)
                methodDefinition.Body.Variables.AddRange(methodDef
                    .Body
                        .Variables
                        .Select(v => v.Clone(targetTypeDeclaringMethod, proxyType)));


            methodDefinition.Parameters.AddRange(method
                .Parameters
                .Select(p => p.Clone(targetTypeDeclaringMethod, proxyType)));


            // call to the field
            // methodDefinition.Body.Instructions.Append(
            // Instruction.Create(OpCodes.Ldarg_0),
            // Instruction.Create(OpCodes.Ldfld, targetBackingField));
            // store parameters
            // HandleParameters(methodDefinition);


            //if the method is override (private override), this means explicit implementation of interfaz, we use the override:
            var methodReference = methodDef.Overrides.Any() ? methodDef.Overrides.First() : method;
            methodReference = CloseMethod(methodReference, targetTypeDeclaringMethod);

            if (autoAddInproxyType)
                proxyType.Methods.Add(methodDefinition);

            return methodDefinition;
        }


        private static void Clone(IEnumerable<CustomAttribute> lst, MethodDefinition method)
        {

            ModuleDefinition _module = method.Module;

            foreach (CustomAttribute item5 in lst)
            {
                if (item5.AttributeType.Name != "CompilerGeneratedAttribute")
                {
                    CustomAttribute newAttribute = new CustomAttribute(_module.Import(item5.Constructor));
                    method.CustomAttributes.Add(newAttribute);

                    foreach (CustomAttributeArgument item in item5.ConstructorArguments)
                    {
                        CustomAttributeArgument arg = new CustomAttributeArgument(_module.Import(item.Type), item.Value);
                        newAttribute.ConstructorArguments.Add(arg);
                    }
                }
            }
        }


        private static MethodReference CloseMethod(MethodReference self, TypeReference targetType)
        {
            if (!targetType.IsGenericInstance) return self;
            var reference = new MethodReference(self.Name, self.ReturnType)
            {
                DeclaringType = targetType,
                HasThis = self.HasThis,
                ExplicitThis = self.ExplicitThis,
                ReturnType = self.ReturnType,

            };

            reference.ReturnType = self.ReturnType; //.CloseGenericParameter(targetType, proxyType);

            foreach (var parameter in self.Parameters)
                reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));

            foreach (var genericParameter in self.GenericParameters)
                reference.GenericParameters.Add(new GenericParameter(genericParameter));

            return reference;
        }

    }
}
